查看原文
其他

工具&技巧 | 经济学圈特供 小刘帮你画专业社会网络图(一)

Dyson 数据Seminar 2021-06-03



写在前面的话


我们的生活中,网络无处不在。社会网络(电话呼叫网络、人脉网络),知识网络(引文网络),科学网络(互联网、交通网络),生物网络(基因调控网络、食物网、神经网络),已经将我们团团包围。而复杂网络还有着更深刻的含义,具体可以参考

➭https://blog.csdn.net/qq997843911/article/details/80162157


“作为一名理科生,我们必须要能透过现象看本质”


这是我高三数学老师陆大师(致敬)经常给我们的教导

经常关注大数据的同学肯定也有看到过各种形形色色的网格图表,它们给我的第一印象是,这么多乱七八糟的线条,能看出什么东西啊!关于这个,大家如有兴趣可以关注我们公司的公众号(微信号:gooddatatech),里面即将推出我们研发部神仙姐姐关于此方面的力作。

面对这么多线条,我理所当然的想到了一个经典、炫酷、无所不能的画图程序:“小海龟”画图。

➭https://www.cnblogs.com/zaric666-chou/p/9127574.html

但是,如果要用小海龟画图来画复杂网络图的话,那么绝大部分同学可是要叫爹叫娘了。为什么呢?

因为我们的眼里都是方块、圆圈和线条。要合理布置这些元素,可不是几十行代码能解决的。

那么,复杂网络图就这么难画了吗?并没有!下面我将分两期内容为大家介绍如何绘制并解读复杂网络图,本期主要介绍绘制社会网络图的一些基础知识,下期则是动手绘制两幅精美的社会网络图。

那么现在,就请大家跟我上我的步伐,一探复杂网络图的究竟吧!

ps:我的工作主要以写后端代码为主,在写这个项目之前,只接触过一些pyplot的代码演示,画图类的代码接触的比较少,所以如有哪里理解不到位的,还请大家多多指点。




networkx库简介


networkx的复杂网络图中,如图一箭头所指,主要有以下三大元素:连线、标签、节点。

(图一)

ps:edge这个单词,一般译作“边缘”,也有“网络连接”的意思。我这边先统一称之为“连线”,便于理解。

显然,节点是这个模型中最重要的元素。一旦节点的位置确定了,其他的两个元素的位置也随之确定,剩下的只剩下调整元素的颜色、大小等属性了。



网格骨架:节点坐标

自古有“人靠衣装”的说法,对于复杂网络图亦是如此。不过,倘若骨架没有调整好,即使你颜色设置得再鲜丽、再动人,要想让人觉得有美感,恐怕也只是想想而已。所以下面我们将着重介绍networkx中最重要的参数----节点坐标,希望大家对以下函数能有一个充分的认识。
在networkx中,有以下几种生成节点坐标的函数:

所有带*号的函数,在注释中都有这么个NOTE:

注意:这个算法目前只在二维数据上有效,并且不会试图减少连线的交叉(This algorithm currently only works in two dimensions and does not try to minimize edge crossings)。

我抽了几个县级的数据样本,用shell_layout函数生成坐标,做出来如图二所示:

(图二)

我们来看看这个shell_layout函数返回的对象长什么样:

{'浦东新区': array([0.2, 0. ]), '嘉定区': array([0.062, 0.19 ]), '闵行区': array([-0.162, 0.118]), ' 建邺区 ': array([-0.162, -0.118]), ' 浦东新区 ': array([ 0.062, -0.19 ]), '建邺区': array([0.4, 0. ]), '浦口区': array([-0.2 , 0.346]), '宝山区': array([-0.2 , -0.346]), ' 浦口区 ': array([0.6, 0. ]), ' 吴中区 ': array([0.185, 0.571]), ' 嘉定区 ': array([-0.485, 0.353]), ' 闵行区 ': array([-0.485, -0.353]), ' 宝山区 ': array([ 0.185, -0.571]), '鼓楼区': array([0.8, 0. ]), '吴中区': array([-0.4 , 0.693]), ' 鼓楼区 ': array([-0.4 , -0.693])}
左右滑动查看更多
也就是说,layout类的函数返回的是一个字典,key是所有节点的label,value是一个只包含两个值的Numpy一维数组。
有这么详细坐标数据,我们可以做的文章其实就很大了。各种各样的微调,YOU NAME IT!
我这里提供两个旋转坐标点的函数,且当抛砖引玉:
# 转自https://blog.csdn.net/qq_38826019/article/details/84233397
# 绕pointx,pointy逆时针旋转def contrarotate(angle,valuex,valuey,pointx,pointy): valuex = np.array(valuex) valuey = np.array(valuey) nRotatex = (valuex-pointx)*math.cos(angle) - (valuey-pointy)*math.sin(angle) + pointx nRotatey = (valuex-pointx)*math.sin(angle) + (valuey-pointy)*math.cos(angle) + pointy return nRotatex, nRotatey# 绕pointx,pointy顺时针旋转def rotate(angle,valuex,valuey,pointx,pointy): valuex = np.array(valuex) valuey = np.array(valuey) sRotatex = (valuex-pointx)*math.cos(angle) + (valuey-pointy)*math.sin(angle) + pointx  sRotatey = (valuey-pointy)*math.cos(angle) - (valuex-pointx)*math.sin(angle) + pointy

左右滑动查看更多

另外,有兴趣的同学可以考虑一下:图二中的箭头长度其实有点长,也就是说每个节点之间的距离有点太大了。这也是在很多画网络模型图的软件中不大方便解决的问题。但是Python中却可以轻松自定义,聪明的你想到了吗?欢迎在留言区留下自己的方法。



网格属性:节点、连线、标签

networkx中有提供可以一步画图的函数如下:
draw(G, pos=None, ax=None, **kwds)draw_networkx(G, pos=None, arrows=True, with_labels=True, **kwds)

左右滑动查看更多

但是我这边不是很推荐,这样画图会使得自己DIY的自由度降低,而且也不方便我们从代码层面认识networkx中的三大属性。
同时,学过pyplot的同学都知道,涉及到画图的函数,会需要配置各种各样的参数。所以,建议经常需要画图的同学在学习的时候,就给自己做一个模板,一劳永逸。


节点

我们可以通过add_node来将节点添加到图对象中,我用到过的一个比较有用的参数是weight,即权重。权重在有些分布算法中有效(例如spring_layout),在有些算法中则看不出明显的效果(例如shell_layout)。
接来下是networkx画节点的函数:
draw_networkx_nodes(G, pos, nodelist=None, node_size=300, node_color='#1f78b4', node_shape='o', alpha=None, cmap=None, vmin=None, vmax=None, ax=None, linewidths=None, edgecolors=None, label=None, **kwds)

左右滑动查看更多

其中,G是networkx中的图对象,pos是前面提到的节点坐标,nodelist是这一次我们想画的节点列表,node_size、node_color、node_shape、alpha分别对应节点大小、节点颜色、节点形状、节点透明度。
如果想画颜色渐变的网格图,那么可以用到cmap参数,有兴趣的同学可以试试以下代码:
import matplotlib.pyplot as pltimport networkx as nx
G = nx.cycle_graph(24)pos = nx.spring_layout(G, iterations=200)nx.draw(G, pos, node_color=range(24), node_size=800, cmap=plt.cm.Blues)plt.show()# 转自https://www.liangzl.com/get-article-detail-557.html

左右滑动查看更多

以上代码使用了matplitlib.pyplot中的colormap来更改不同节点的颜色。但我们如果要像图二中那样给不同的节点设置不同的大小、颜色、形状、透明度等呢?我推荐的方法是给节点做自定义分类,然后通过for循环一组一组的画节点,具体流程可以参考后面的实例部分。
另外,关于node_shape,以下是我搜到的一些节点形状对应的代号:
. Point marker, Pixel markero Circle markerv Triangle down marker^ Triangle up marker< Triangle left marker> Triangle right marker1 Tripod down marker2 Tripod up marker3 Tripod left marker4 Tripod right markers Square markerp Pentagon marker* Star markerh Hexagon markerH Rotated hexagon D Diamond markerd Thin diamond marker| Vertical line (vlinesymbol) marker_ Horizontal line (hline symbol) marker+ Plus markerx Cross (x) marker
左右滑动查看更多


连线

添加连线的函数是add_edge,没有发现比较有用的参数。我们直接来看看画图部分吧。
draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=None, arrowstyle='-|>', arrowsize=10, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, node_size=300, nodelist=None, node_shape='o', connectionstyle=None, **kwds)

左右滑动查看更多

G,pos同上,edgelist对应这段代码想要画出来的连线,width、edge_color、style、alpha分别对应连线粗细、连线颜色、连线形状、连线透明度,如果图对象为“有向图”(具体情况参考实例),arrowstyle则表示箭头的形状(如图二中的箭头)。
除此之外,我这边列举了几个style参数所对应的连线形状:
solid - 实线dashed -- 短线dashdot -.-.短点相间线dotted:..... 虚点线
左右滑动查看更多


标签

标签不需要提前添加,可以直接画出来。

draw_networkx_labels(G, pos, labels=None, font_size=12, font_color='k', font_family='sans-serif', font_weight='normal', alpha=None, bbox=None, ax=None, **kwds)
左右滑动查看更多
draw_networkx_edge_labels(G, pos, edge_labels=None, label_pos=0.5, font_size=10, font_color='k', font_family='sans-serif', font_weight='normal', alpha=None, bbox=None, ax=None, rotate=True, **kwds)

左右滑动查看更多

有两种标签,节点的与连线的。大家从上文读下来的话,大概都看得出不同参数的意思了吧。我这边就提一提label_pos,他的意思是将这个标签放在连线上的哪个位置,如0=head, 0.5=center, 1=tail。


补充

我这边还想多补充一个内容,节点、连线、标签中全部都有涉及颜色的设定。而使用字母表示颜色的话,变数非常有限。不是本文推荐的方法,我只列举几个常用的:“k”黑色,“r”红色,“b”蓝色,“y”黄色,“w”白色。
除了字母颜色,networkx还支持十六进制的颜色数。由于十六进制的颜色值无法方便的进行数值运算,我使用的是RBG颜色值,可以使用一下这个函数进行转换:
rgb_to_hex = lambda rgb: '#' + ''.join([str(hex(num))[-2:].replace('x','0').upper() for num in rgb])

左右滑动查看更多

传入的参数rgb应该是一个三元列表或元组,分别对应RBG中的红色、蓝色、绿色。




后记


本期首先带大家了解了networks库中复杂网络图的三类基本元素节点、连线、标签,以及绘制这三类元素形状、颜色、大小的函数,下一期我就会依据以上基础知识手把手带领大家完整地作出一幅社会网络分析图
在此之前,鉴于我们的读者中有不少是还未走上工作岗位的学生,而这次的代码涉及“图表展示”,我想给不了解产品经理与程序员“爱恨交织”的同学写几句话。

一个团队中,必然会有项目的设计者与执行者,图表设计一般由设计者完成,我们一般属于执行者完成相应的需求。由于交流中存在“信息失真”、需求与实现能力不对等,所以“开发部门”与“产品部门”的矛盾是永远都会存在的。


更何况,在图表、软件、网页设计常常因为不同人审美的原因,会产生多次需求的变动,这就会带来工作量的增加进而延误项目进度。


在很多大型团队中,情况是这样的:

也有这样的(例如我们的团队。。。。。。。。开玩笑)

那么,一次小小的变更需求,会带来多少工作量的增加呢?
我是做数据清洗的,我就以数据清洗为例。
如果有一个数据清洗的需求,核心代码当然是数据处理部分,流程如下:

如果有两个需求变更,大家可以分别思考一下难度。

1、输出报表的结构变化,需要多增加十个图。

2、原始CSV数据的格式变为JSON并增加了数据维度。

对于第一种情况,可以增加如下流程:

那么我只需要增加一个画图模块,在报表里插入一些放置图片的代码就好了(就拿reportlab库来说,报表里内容的排版顺序都对应着程序里的代码顺序,哪里需要添加内容,就在对应的哪几行代码之间新加代码)。

但是对于第二种情况,就复杂多了,可以说,需要把绝大部分代码全部重写一遍。

数据格式的变更并不难,但是数据维度的增加有可能会导致数据处理过程的重写。所以说,合理的估量变更需求所带来的成本,是设计者与执行者都需要考量的。
那么,我在这里介绍一下我们团队合作经验,可能只适用于小团队,且当抛砖引玉。
首先,我们做的是数据产品,双方都参与产品了开发流程的细化。

我们在做工作交流的时候,都会尽可能的交流更细的层面上的数据处理方法。例如日期逻辑修正,哪些日期逻辑问题是我们经过处理的。而不能像“我们做过了数据逻辑修正”这么一笔带过。
为什么呢?因为我们代码中很多步骤,很有可能是我们一厢情愿加进去的,很可能产生我们意想不到的问题。例如,在企业信息变更表中,有很多数据都是空的。如果我们为了自己程序的稳定,图简单把空数据删除的话,很有可能造成信息的丢失。因为数据为空,变更日期还是在的。她能证明这个企业在这个时间发生了什么事,只是我们不知道是什么事而已,对于某些学术研究还是有价值的。
所以说,废物虽然是废物,但是总有变废为宝的可能性的。有多少利用价值,设计者肯定比我们更加清楚。所以我们有必要让大家都知道我们“干了些什么”。
同时,这也是一个做CodeReview的好机会。由于流程已经细化到每个小步骤了,所以直接展示代码能更加节约时间,也能避免把时间花在做PPT、画流程图上面。我们团队的人基本都学过Python基础(骄傲!!),所以看代码也不会特别困难,还能相互学习,相互促进。
更多的,我们的团队在完成一批代码之后,都会用一小部分数据做测试,检验样本结果。虽然这是一个理所应当的操作,但是据我所知,很多人都在项目进度的逼迫下,有时会抱着侥幸心理跳过这一步(我们团队血的教训)。我们的数据项目常常一运行就是一两周。待完成以后再发现错误,时间成本的损失是不可估量的。
本期内容就是这样,下期内容将手把手教大家绘制精美的社会网络图,敬请关注~





►往期推荐

回复【Python】👉简单有用易上手

回复【学术前沿】👉机器学习丨大数据

回复【数据资源】👉公开数据

回复【可视化】👉你心心念念的数据呈现

回复【老姚专栏】👉老姚趣谈值得一看


►一周热文

特别推荐丨老姚专栏:漫谈小样本问题

数据呈现丨轻松用 Seaborn 进行数据可视化

学术前沿丨大数据在劳动力市场研究中的应用与展望

学术前沿丨当计量经济学遭遇机器学习(四):高维回归之LASSO

数据呈现丨中文文本可视化:用 Python 轻松制作词云




数据Seminar

这里是大数据、分析技术与学术研究的三叉路口


作者:Dyson(刘颖波)审阅:威武哥(叶武威)、江东(刘良东)编辑:青酱




    欢迎扫描👇二维码添加关注    


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存